/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_frontEnd.cpp - front-end renderer
//


#include "r_local.h"


/*
 ==============================================================================

 FRAME MEMORY ALLOCATION

 ==============================================================================
*/


/*
 ==================
 R_AllocFrameMemory
 ==================
*/
void R_AllocFrameMemory (){

	rg.frameMemory.data = (byte *)Mem_Alloc16(MAX_FRAME_MEMORY, TAG_RENDERER);
}

/*
 ==================
 R_ClearFrameMemory
 ==================
*/
void R_ClearFrameMemory (){

	rg.frameMemory.used = 0;

	rg.frameMemory.allocs = 0;
}

/*
 ==================
 R_FrameAlloc

 This data will be automatically freed when the current frame completes.

 This should only be called by the front-end. The back-end shouldn't need to
 allocate memory.

 All frame temporary data is allocated here.
 ==================
*/
void *R_FrameAlloc (int size){

	byte	*data;

	// Align to 16-byte boundary
	size = ALIGN(size, 16);

	// Allocate
	if (rg.frameMemory.used + size > MAX_FRAME_MEMORY)
		Com_Error(false, "R_FrameAlloc: failed on allocation of %i bytes\n", size);

	data = rg.frameMemory.data + rg.frameMemory.used;

	rg.frameMemory.used += size;

	// Update statistics
	rg.frameMemory.allocs++;

	if (rg.frameMemory.used > rg.frameMemory.highwater)
		rg.frameMemory.highwater = rg.frameMemory.used;

	return data;
}

/*
 ==================
 R_ClearedFrameAlloc

 This data will be automatically freed when the current frame completes.

 This should only be called by the front-end. The back-end shouldn't need to
 allocate memory.

 All frame temporary data is allocated here.
 ==================
*/
void *R_ClearedFrameAlloc (int size){

	byte	*data;

	// Align to 16-byte boundary
	size = ALIGN(size, 16);

	// Allocate
	if (rg.frameMemory.used + size > MAX_FRAME_MEMORY)
		Com_Error(false, "R_ClearedFrameAlloc: failed on allocation of %i bytes\n", size);

	data = rg.frameMemory.data + rg.frameMemory.used;

	rg.frameMemory.used += size;

	// Update statistics
	rg.frameMemory.allocs++;

	if (rg.frameMemory.used > rg.frameMemory.highwater)
		rg.frameMemory.highwater = rg.frameMemory.used;

	// Clear the block
	Mem_Fill(data, 0, size);

	return data;
}


/*
 ==============================================================================

 VIEW FRUSTUM CULLING

 ==============================================================================
*/


/*
 ==================
 R_CullBounds
 ==================
*/
int R_CullBounds (const glqBounds &bounds, int planeBits){

	glqPlane	*plane;
	int			side, cullBits;
	int			i;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = rg.viewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = plane->BoundsOnSide(bounds);

		if (side == PLANESIDE_BACK){
			rg.pc.cullBoundsOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.cullBoundsClip++;
	else
		rg.pc.cullBoundsIn++;

	return cullBits;
}

/*
 ==================
 R_CullLocalBounds
 ==================
*/
int R_CullLocalBounds (const glqBounds &bounds, const glqVec3 &origin, const glqMat3 &axis, int planeBits){

	glqPlane	*plane;
	glqVec3		corner, worldCorners[8];
	int			side, cullBits;
	int			i, j;

	cullBits = CULL_IN;

	// Compute the corner points
	for (i = 0; i < 8; i++){
		corner[0] = (i & 1) ? bounds[0][0] : bounds[1][0];
		corner[1] = (i & 2) ? bounds[0][1] : bounds[1][1];
		corner[2] = (i & 4) ? bounds[0][2] : bounds[1][2];

		// Transform into world space
		R_LocalPointToWorld(corner, worldCorners[i], origin, axis);
	}

	// Check against frustum planes
	for (i = 0, plane = rg.viewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = 0;

		for (j = 0; j < 8; j++){
			side |= plane->PointOnSide(worldCorners[j]);

			if (side == PLANESIDE_CROSS)
				break;
		}

		if (side == PLANESIDE_BACK){
			rg.pc.cullBoundsOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.cullBoundsClip++;
	else
		rg.pc.cullBoundsIn++;

	return cullBits;
}

/*
 ==================
 R_CullSphere
 ==================
*/
int R_CullSphere (const glqSphere &sphere, int planeBits){

	glqPlane	*plane;
	int			side, cullBits;
	int			i;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = rg.viewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = plane->SphereOnSide(sphere);

		if (side == PLANESIDE_BACK){
			rg.pc.cullSphereOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.cullSphereClip++;
	else
		rg.pc.cullSphereIn++;

	return cullBits;
}

/*
 ==================
 R_CullLocalSphere
 ==================
*/
int R_CullLocalSphere (const glqSphere &sphere, const glqVec3 &origin, const glqMat3 &axis, int planeBits){

	glqPlane	*plane;
	glqVec3		worldCenter;
	int			side, cullBits;
	int			i;

	cullBits = CULL_IN;

	// Transform into world space
	R_LocalPointToWorld(sphere.Center(), worldCenter, origin, axis);

	// Check against frustum planes
	for (i = 0, plane = rg.viewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = plane->PointOnSide(worldCenter, sphere.Radius());

		if (side == PLANESIDE_BACK){
			rg.pc.cullSphereOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.cullSphereClip++;
	else
		rg.pc.cullSphereIn++;

	return cullBits;
}

/*
 ==================
 R_CullLine
 ==================
*/
int R_CullLine (const glqVec3 &start, const glqVec3 &end, float epsilon, int planeBits){

	glqPlane	*plane;
	int			side, cullBits;
	int			i;

	cullBits = CULL_IN;

	// Check against frustum planes
	for (i = 0, plane = rg.viewParms.frustum; i < NUM_FRUSTUM_PLANES; i++, plane++){
		if (!(planeBits & BIT(i)))
			continue;

		side = plane->LineOnSide(start, end, epsilon);

		if (side == PLANESIDE_BACK){
			rg.pc.cullLineOut++;
			return CULL_OUT;
		}

		if (side == PLANESIDE_CROSS)
			cullBits |= BIT(i);
	}

	if (cullBits != CULL_IN)
		rg.pc.cullLineClip++;
	else
		rg.pc.cullLineIn++;

	return cullBits;
}


/*
 ==============================================================================

 VIEW RENDERING

 ==============================================================================
*/


/*
 ==================
 R_PointInLeaf
 ==================
*/
static leaf_t *R_PointInLeaf (const glqVec3 &point){
	
	node_t	*node;
	int		side;

	if (!rg.world)
		Com_Error(false, "R_PointInLeaf: NULL world");

	node = rg.world->nodes;

	while (1){
		if (node->contents < 0)
			return (leaf_t *)node;

		side = node->plane->PointOnSide(point);

		if (side == PLANESIDE_FRONT)
			node = node->children[0];
		else
			node = node->children[1];
	}

	return (leaf_t *)node;
}

/*
 ==================
 R_DecompressPVS
 ==================
*/
static byte *R_DecompressPVS (const byte *in){

	static byte pvs[BSP_MAX_LEAFS/8];
	byte		*out;
	int			c, row;

	row = (rg.world->numLeafs + 7) >> 3;	
	out = pvs;

	if (!in){
		// No pvs info, so make all visible
		while (row){
			*out++ = 0xFF;
			row--;
		}

		return pvs;		
	}

	do {
		if (*in){
			*out++ = *in++;
			continue;
		}

		c = in[1];
		in += 2;
		while (c){
			*out++ = 0;
			c--;
		}
	} while (out - pvs < row);

	return pvs;
}

/*
 ==================
 R_LeafPVS
 ==================
*/
static byte *R_LeafPVS (leaf_t *leaf){

	if (!leaf->compressedPVS || leaf == rg.world->leafs)
		return rg.world->noPVS;

	return R_DecompressPVS(leaf->compressedPVS);
}

/*
 ==================
 R_MarkLeaves

 The last leaf is always invalid, so we skip it (is this correct?)

 Mark the leaves and nodes that are in the PVS for the current view leaf
 ==================
*/
void R_MarkLeaves (){

	leaf_t	*leaf;
	node_t	*node;
	byte	*pvs;
	int		i;

	if (!rg.world)
		Com_Error(false, "R_MarkLeaves: NULL world");

	// Development tool
	if (r_lockVisibility->integerValue)
		return;

	// Get the current view leaf
	if (rg.viewParms.viewType == VIEW_MAIN)
		leaf = R_PointInLeaf(rg.renderView.origin);
	else
		leaf = R_PointInLeaf(rg.subviewParms.pvsOrigin);

	rg.oldViewLeaf = rg.viewLeaf;
	rg.viewLeaf = leaf;

	// If the view leaf haven't changed, we don't need to mark
	// everything again
	if (!r_skipVisibility->integerValue && rg.viewLeaf == rg.oldViewLeaf)
		return;

	rg.visCount++;
	rg.oldViewLeaf = rg.viewLeaf;

	// Mark everything if needed
	if (r_skipVisibility->integerValue || !rg.world->pvs || !rg.viewLeaf){
		for (i = 0, leaf = rg.world->leafs; i < rg.world->numLeafs; i++, leaf++)
			leaf->visCount = rg.visCount;
		for (i = 0, node = rg.world->nodes; i < rg.world->numNodes; i++, node++)
			node->visCount = rg.visCount;

		return;
	}

	// Get the PVS for the current view leaf
	pvs = R_LeafPVS(rg.viewLeaf);

	// Mark the leaves and nodes that are visible from the current view leaf
	for (i = 0, leaf = rg.world->leafs; i < rg.world->numLeafs - 1; i++, leaf++){
		// Check the PVS
		if (!(pvs[i >> 3] & BIT(i & 7)))
			continue;

		node = (node_t *)&leaf[1];

		do {
			if (node->visCount == rg.visCount)
				break;
			node->visCount = rg.visCount;

			node = node->parent;
		} while (node);
	}
}

/*
 ==================
 R_SetFarClip
 ==================
*/
void R_SetFarClip (){

	static int	cornerIndices[6][4] = {{3, 2, 6, 7}, {0, 1, 5, 4}, {2, 3, 1, 0}, {4, 5, 7, 6}, {1, 3, 7, 5}, {2, 0, 4, 6}};
	glqVec3		corners[8];
	glqVec3		points[2][MAX_POLYGON_POINTS];
	int			numPoints;
	glqPlane	*plane;
	float		distance, maxDistance = 0.0f;
	float		z, w;
	int			pingPong = 0;
	int			i, j;

	if (r_zFar->floatValue > r_zNear->floatValue)
		return;

	// If we have visible leaves
	if (!rg.viewParms.visBounds.IsCleared()){
		rg.viewParms.visBounds.ToPoints(corners);

		// Clip the visible volume to the view frustum
		for (i = 0; i < 6; i++){
			numPoints = 4;

			points[pingPong][0] = corners[cornerIndices[i][0]];
			points[pingPong][1] = corners[cornerIndices[i][1]];
			points[pingPong][2] = corners[cornerIndices[i][2]];
			points[pingPong][3] = corners[cornerIndices[i][3]];

			for (j = 0; j < NUM_FRUSTUM_PLANES; j++){
				if (!(rg.viewParms.planeBits & BIT(j)))
					continue;

				if (!R_ClipPolygon(numPoints, points[pingPong], rg.viewParms.frustum[j], ON_EPSILON, &numPoints, points[!pingPong]))
					break;

				pingPong ^= 1;
			}

			if (j != NUM_FRUSTUM_PLANES)
				continue;

			// Find the maximum distance to the near clip plane
			plane = &rg.viewParms.frustum[FRUSTUM_NEAR];

			for (j = 0; j < numPoints; j++){
				distance = plane->Distance(points[pingPong][j]);

				if (distance > maxDistance)
					maxDistance = distance;
			}
		}
	}

	// Compute distance to far clip plane
	rg.viewParms.zFar = r_zNear->floatValue + maxDistance + 1.0f;
	if (rg.viewParms.zFar < 50.0f)
		rg.viewParms.zFar = 50.0f;

	if (r_showFarClip->integerValue)
		Com_Printf("zFar: %.2f\n", rg.viewParms.zFar);

	z = -(rg.viewParms.zFar + r_zNear->floatValue) / (rg.viewParms.zFar - r_zNear->floatValue);
	w = -2.0f * rg.viewParms.zFar * r_zNear->floatValue / (rg.viewParms.zFar - r_zNear->floatValue);

	// Modify the projection matrix
	rg.viewParms.projectionMatrix[2][2] = z;
	rg.viewParms.projectionMatrix[3][2] = w;

	// Modify the modelview-projection matrix
	rg.viewParms.modelviewProjectionMatrix[0][2] = -rg.renderView.axis[0][0] * z;
	rg.viewParms.modelviewProjectionMatrix[1][2] = -rg.renderView.axis[0][1] * z;
	rg.viewParms.modelviewProjectionMatrix[2][2] = -rg.renderView.axis[0][2] * z;
	rg.viewParms.modelviewProjectionMatrix[3][2] = rg.renderView.origin.Dot(rg.renderView.axis[0]) * z + w;
}

/*
 ==================
 R_SetupViewportAndScissor
 ==================
*/
static void R_SetupViewportAndScissor (){

	renderCrop_t	*renderCrop;
	glqBounds		bounds;
	int				x, y, w, h;

	if (rg.envShotRendering){
		rg.viewParms.viewport.Set(0, 0, rg.envShotSize, rg.envShotSize);
		rg.viewParms.scissor.Set(0, 0, rg.envShotSize, rg.envShotSize);

		return;
	}

	renderCrop = &rg.renderCrops[rg.currentRenderCrop];

	// Scale the coordinates and correct the aspect ratio if needed
	R_AdjustHorzCoordsInt(rg.renderView.horzAdjust, rg.renderView.horzPercent, rg.renderView.x, rg.renderView.width, &x, &w);
	R_AdjustVertCoordsInt(rg.renderView.vertAdjust, rg.renderView.vertPercent, rg.renderView.y, rg.renderView.height, &y, &h);

	// Convert origin from top-left to bottom-left
	y = renderCrop->height - (y + h);

	// Set up the viewport
	rg.viewParms.viewport.Set(x, y, x + w, y + h);
	rg.viewParms.viewport.Clip(renderCrop->rect);

	// Set up the scissor
	if (r_skipScissors->integerValue){
		rg.viewParms.scissor = rg.viewParms.viewport;
		return;
	}

	if (rg.viewParms.viewType == VIEW_MAIN || rg.viewParms.viewType == VIEW_REMOTE)
		rg.viewParms.scissor = rg.viewParms.viewport;
	else {
		// Transform into screen space
		R_TransformDeviceToScreen(rg.subviewParms.ndcBounds[0], bounds[0], rg.viewParms.viewport);
		R_TransformDeviceToScreen(rg.subviewParms.ndcBounds[1], bounds[1], rg.viewParms.viewport);

		// Set up the scissor
		rg.viewParms.scissor.FromBounds(bounds);
		rg.viewParms.scissor.ExpandSelf(1);
		rg.viewParms.scissor.Clip(rg.viewParms.viewport);

		// Flip it horizontally if needed
		if (rg.viewParms.viewType == VIEW_MIRROR){
			x = rg.viewParms.viewport.x2 - rg.viewParms.scissor.x2;
			w = rg.viewParms.scissor.x2 - rg.viewParms.scissor.x1;

			rg.viewParms.scissor.x1 = rg.viewParms.viewport.x1 + x;
			rg.viewParms.scissor.x2 = rg.viewParms.viewport.x1 + x + w;
		}
	}
}

/*
 ==================
 R_SetupFOV
 ==================
*/
static void R_SetupFOV (){

	float	f;

	if (rg.envShotRendering){
		rg.viewParms.fovX = 90.0f;
		rg.viewParms.fovY = 90.0f;
		rg.viewParms.fovScale = 1.0f;

		return;
	}

	rg.viewParms.fovX = rg.renderView.fovX;
	rg.viewParms.fovY = rg.renderView.fovY;
	rg.viewParms.fovScale = rg.renderView.fovScale;

	// Correct the aspect ratio if needed
	if (rg.viewParms.viewType != VIEW_MAIN)
		return;

	if (rg.viewParms.viewport.Width() * rg.renderView.height > rg.viewParms.viewport.Height() * rg.renderView.width){
		f = rg.viewParms.viewport.Height() / glqMath::Tan(rg.viewParms.fovY / 360.0f * M_PI);
		rg.viewParms.fovX = glqMath::ATan(rg.viewParms.viewport.Width(), f) * 360.0f / M_PI;
	}
	else if (rg.viewParms.viewport.Width() * rg.renderView.height < rg.viewParms.viewport.Height() * rg.renderView.width){
		f = rg.viewParms.viewport.Width() / glqMath::Tan(rg.viewParms.fovX / 360.0f * M_PI);
		rg.viewParms.fovY = glqMath::ATan(rg.viewParms.viewport.Height(), f) * 360.0f / M_PI;
	}
}

/*
 ==================
 R_SetupFrustum
 ==================
*/
static void R_SetupFrustum (){

	float	s, c;

	rg.viewParms.planeBits = 0;

	// Set up the left/right clip planes
	glqMath::SinCos(rg.viewParms.fovX * (M_PI_HALF / 180.0f), s, c);

	rg.viewParms.planeBits |= (BIT(FRUSTUM_LEFT) | BIT(FRUSTUM_RIGHT));

	rg.viewParms.frustum[FRUSTUM_LEFT].SetNormal(rg.renderView.axis[0] * s - rg.renderView.axis[1] * c);
	rg.viewParms.frustum[FRUSTUM_LEFT].SetDist(rg.renderView.origin.Dot(rg.viewParms.frustum[FRUSTUM_LEFT].Normal()));
	rg.viewParms.frustum[FRUSTUM_LEFT].SetSignBits();

	rg.viewParms.frustum[FRUSTUM_RIGHT].SetNormal(rg.renderView.axis[0] * s + rg.renderView.axis[1] * c);
	rg.viewParms.frustum[FRUSTUM_RIGHT].SetDist(rg.renderView.origin.Dot(rg.viewParms.frustum[FRUSTUM_RIGHT].Normal()));
	rg.viewParms.frustum[FRUSTUM_RIGHT].SetSignBits();

	// Set up the top/bottom clip planes
	glqMath::SinCos(rg.viewParms.fovY * (M_PI_HALF / 180.0f), s, c);

	rg.viewParms.planeBits |= (BIT(FRUSTUM_TOP) | BIT(FRUSTUM_BOTTOM));

	rg.viewParms.frustum[FRUSTUM_TOP].SetNormal(rg.renderView.axis[0] * s - rg.renderView.axis[2] * c);
	rg.viewParms.frustum[FRUSTUM_TOP].SetDist(rg.renderView.origin.Dot(rg.viewParms.frustum[FRUSTUM_TOP].Normal()));
	rg.viewParms.frustum[FRUSTUM_TOP].SetSignBits();

	rg.viewParms.frustum[FRUSTUM_BOTTOM].SetNormal(rg.renderView.axis[0] * s + rg.renderView.axis[2] * c);
	rg.viewParms.frustum[FRUSTUM_BOTTOM].SetDist(rg.renderView.origin.Dot(rg.viewParms.frustum[FRUSTUM_BOTTOM].Normal()));
	rg.viewParms.frustum[FRUSTUM_BOTTOM].SetSignBits();

	// Set up the near clip plane
	rg.viewParms.planeBits |= BIT(FRUSTUM_NEAR);

	rg.viewParms.frustum[FRUSTUM_NEAR].SetNormal(rg.renderView.axis[0]);
	rg.viewParms.frustum[FRUSTUM_NEAR].SetDist(rg.renderView.origin.Dot(rg.viewParms.frustum[FRUSTUM_NEAR].Normal()) + r_zNear->floatValue);
	rg.viewParms.frustum[FRUSTUM_NEAR].SetSignBits();

	// Set up the user clip plane if needed
	if (rg.viewParms.viewType == VIEW_MIRROR){
		rg.viewParms.planeBits |= BIT(FRUSTUM_USER);

		rg.viewParms.frustum[FRUSTUM_USER] = rg.subviewParms.clipPlane;
	}
}

/*
 ==================
 R_SetupMatrices
 ==================
*/
static void R_SetupMatrices (){

	// Compute the projection matrix
	rg.viewParms.projectionMatrix[0][0] = 1.0f / glqMath::Tan(rg.viewParms.fovX * (M_PI / 360.0f));
	rg.viewParms.projectionMatrix[0][1] = 0.0f;
	rg.viewParms.projectionMatrix[0][2] = 0.0f;
	rg.viewParms.projectionMatrix[0][3] = 0.0f;
	rg.viewParms.projectionMatrix[1][0] = 0.0f;
	rg.viewParms.projectionMatrix[1][1] = 1.0f / glqMath::Tan(rg.viewParms.fovY * (M_PI / 360.0f));
	rg.viewParms.projectionMatrix[1][2] = 0.0f;
	rg.viewParms.projectionMatrix[1][3] = 0.0f;
	rg.viewParms.projectionMatrix[2][0] = 0.0f;
	rg.viewParms.projectionMatrix[2][1] = 0.0f;
	rg.viewParms.projectionMatrix[2][2] = -(rg.viewParms.zFar + r_zNear->floatValue) / (rg.viewParms.zFar - r_zNear->floatValue);
	rg.viewParms.projectionMatrix[2][3] = -1.0f;
	rg.viewParms.projectionMatrix[3][0] = 0.0f;
	rg.viewParms.projectionMatrix[3][1] = 0.0f;
	rg.viewParms.projectionMatrix[3][2] = -2.0f * rg.viewParms.zFar * r_zNear->floatValue / (rg.viewParms.zFar - r_zNear->floatValue);
	rg.viewParms.projectionMatrix[3][3] = 0.0f;

	if (rg.viewParms.primaryView && r_zFar->floatValue <= r_zNear->floatValue){
		rg.viewParms.projectionMatrix[2][2] = -0.999f;
		rg.viewParms.projectionMatrix[3][2] = -2.0f * r_zNear->floatValue;
	}

	// Compute the modelview matrix
	rg.viewParms.modelviewMatrix[0][0] = -rg.renderView.axis[1][0];
	rg.viewParms.modelviewMatrix[0][1] = rg.renderView.axis[2][0];
	rg.viewParms.modelviewMatrix[0][2] = -rg.renderView.axis[0][0];
	rg.viewParms.modelviewMatrix[0][3] = 0.0f;
	rg.viewParms.modelviewMatrix[1][0] = -rg.renderView.axis[1][1];
	rg.viewParms.modelviewMatrix[1][1] = rg.renderView.axis[2][1];
	rg.viewParms.modelviewMatrix[1][2] = -rg.renderView.axis[0][1];
	rg.viewParms.modelviewMatrix[1][3] = 0.0f;
	rg.viewParms.modelviewMatrix[2][0] = -rg.renderView.axis[1][2];
	rg.viewParms.modelviewMatrix[2][1] = rg.renderView.axis[2][2];
	rg.viewParms.modelviewMatrix[2][2] = -rg.renderView.axis[0][2];
	rg.viewParms.modelviewMatrix[2][3] = 0.0f;
	rg.viewParms.modelviewMatrix[3][0] = rg.renderView.origin.Dot(rg.renderView.axis[1]);
	rg.viewParms.modelviewMatrix[3][1] = -rg.renderView.origin.Dot(rg.renderView.axis[2]);
	rg.viewParms.modelviewMatrix[3][2] = rg.renderView.origin.Dot(rg.renderView.axis[0]);
	rg.viewParms.modelviewMatrix[3][3] = 1.0f;

	// Compute the modelview-projection matrix
	rg.viewParms.modelviewProjectionMatrix = rg.viewParms.projectionMatrix * rg.viewParms.modelviewMatrix;
}

/*
 ==================
 R_RenderView
 ==================
*/
void R_RenderView (bool primaryView, int viewType){

	// Bump view count
	rg.viewCount++;

	// Set up the view
	rg.viewParms.primaryView = primaryView;
	rg.viewParms.viewType = viewType;

	if (r_zFar->floatValue > r_zNear->floatValue)
		rg.viewParms.zFar = r_zFar->floatValue;
	else
		rg.viewParms.zFar = FAR_PLANE_DISTANCE;

	rg.viewParms.floatTime = MS2SEC(rg.renderView.time);

	// Set up the viewport and scissor
	R_SetupViewportAndScissor();

	if (!rg.viewParms.viewport.Size() || !rg.viewParms.scissor.Size())
		return;

	// Set up FOV, frustum, and matrices
	R_SetupFOV();
	R_SetupFrustum();
	R_SetupMatrices();

	// Generate draw surfaces
	R_GenerateDrawSurfaces();
	R_GenerateDrawLights();

	// Update post-process parameters if needed
	R_UpdatePostProcess();

	// Render a subview if needed
	if (R_RenderSubview()){
		if (r_subviewOnly->integerValue)
			return;
	}

	// Add the render view command
	R_AddRenderViewCommand();
}